package ru.nosport.matrixaria.modules.core.rmh;

import org.apache.commons.collections.buffer.CircularFifoBuffer;

import java.util.*;

/**
 * CircularFifoMap is a structure, similar to random access cache table.
 * It has fixed capacity. Adding new objects while full causes least recently inserted objects
 * to be removed from the Map.
 *
 * Map size cannot be changed at runtime.
 *
 *
 * <pre>
 * User: mihon
 * Date: 16.07.2007
 * Time: 16:06:07
 * </pre>
 */
public class CircularFifoMap<K, V> implements Map<K, V> {
	private Map<K, V> results;
	private final CircularFifoBuffer resKeys;


	/**
	 * Create a map with default size.
	 */
	public CircularFifoMap() {
		this(32);
	}

	/**
	 * Create a map with the size specified.
	 *
	 * @param _size Maximum number of objects to store.
	 */
	public CircularFifoMap(int _size) {
		this.results = new HashMap<K, V>(_size);
		this.resKeys = new CircularFifoBuffer(_size);
	}

	/**
	 * Create a Map from the specified collecton.
	 *
	 * @param _map Source data.
	 */
	public CircularFifoMap(Map<K, V> _map) {
		this.results = new HashMap<K, V>(_map);
		this.resKeys = new CircularFifoBuffer(this.results.keySet());
	}

	public void clear() {
		this.results.clear();
		this.resKeys.clear();
	}

	public Set<K> keySet() {
		return this.results.keySet();
	}


	public Set<Entry<K, V>> entrySet() {
		return this.results.entrySet();
	}

	public boolean isEmpty() {
		return this.results.isEmpty();
	}

	/**
	 * Adds an object to the Map.
	 *
	 * If Map contains an object associated with the key given, the old value is returned while being replaced
	 * with the new one.
	 * If Map is full, the least recently object will be discarded while adding a new one.
	 *
	 * @param key Key to associate with the object.
	 * @param value Object to store in the map
	 * @return the previous value associated with key, or null if there was no mapping for key.
	 */
	synchronized
	public V put(K key, V value) {
		if (this.resKeys.isFull() && !this.results.containsKey(key)) {
			this.results.remove(this.resKeys.get());
		}
		if (!this.resKeys.contains(key)) {
			this.resKeys.add(key);
		}
		return this.results.put(key, value);
	}

	/**
	 * Adds all objects to the Map.
	 *
	 * Existing ones will be replaced. If Map capacity is not large enough to store all new values, the least recently
	 * added key-value pairs will be removed from the map.
	 *
	 * @param m Data to put in the Map.
	 */
	public void putAll(Map<? extends K, ? extends V> m) {
		for (K key : m.keySet()) {
			this.put(key, m.get(key));
		}
	}

	public int size() {
		return this.results.size();
	}

	public Collection<V> values() {
		return this.results.values();
	}


	public boolean containsKey(Object key) {
		return this.results.containsKey(key);
	}

	public boolean containsValue(Object value) {
		return this.results.containsValue(value);
	}

	public V get(Object key) {
		return this.results.get(key);
	}

	synchronized
	public V remove(Object key) {
		this.resKeys.remove(key);
		return this.results.remove(key);
	}

	/**
	 * @return maximum number of objects to be stored in the Map at the same time.
	 */
	public int getMaxSize() {
		return this.resKeys.maxSize();
	}
}

